﻿///
/// Copyright (c) 2008, Aaron G. Daisley-Harrison
/// All rights reserved.
/// 
/// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
/// 
/// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
/// 
/// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
/// 
/// * Neither the name of Daisley-Harison Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
/// 
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
/// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
/// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
/// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
/// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
/// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
/// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
/// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Media.Media3D;
using System.Windows.Media;
using System.Windows;
using System.Diagnostics;

namespace DaisleyHarrison.Wavefront
{
    internal class Parser : IParseContext
    {
        public const char CHAR_LINE_CONTINUATION = '\\';
        private StreamReader _reader;
        private string _lineBuffer;
        private int _linePosition;
        private int _lineNumber;
        private StringBuilder _token = new StringBuilder();
        private Stack<string> _tokenStack;
        private Dictionary<string, ParseDelegate> _parsers;
        private Dictionary<string, string> _parameters;
        public Parser()
        {
            _parsers = new Dictionary<string, ParseDelegate>();
            _token = new StringBuilder();
            _tokenStack = new Stack<string>();
            RegisterHandler(TokenConstants.TOKEN_UNSUPPORTED, parseUnsupported);
            RegisterHandler(TokenConstants.TOKEN_EOL, parseEndOfLine);
        }
        private void Initialize(StreamReader reader)
        {
            _reader = reader;
            _lineBuffer = null;
            _lineNumber = 0;
            _linePosition = 0;
            _token.Length = 0;
            _tokenStack.Clear();
        }

        #region ParseContext
        public int LineNumber
        {
            get
            {
                return _lineNumber;
            }
        }
        public int LinePosition
        {
            get
            {
                return _linePosition;
            }
        }
        public string GetParemeter(string name)
        {
            string value;

            if (_parameters != null && _parameters.TryGetValue(name, out value))
            {
                return value;
            }
            else
            {
                return null;
            }
        }
        public void SetParemeter(string name, string value)
        {
            if (_parameters == null)
            {
                _parameters = new Dictionary<string, string>();
            }
            _parameters[name] = value;
        }
        public void RegisterHandler(string lineType, ParseDelegate parser)
        {
            _parsers[lineType] = parser;
        }
        public void PushToken(string token)
        {
            _tokenStack.Push(token);
        }
        public string PeekToken()
        {
            if (_tokenStack.Count > 0)
            {
                return _tokenStack.Peek();
            }
            else
            {
                string token = ((IParseContext)this).NextToken();
                ((IParseContext)this).PushToken(token);
                return token;
            }
        }
        public string NextToken()
        {
            if (_tokenStack.Count > 0)
            {
                return _tokenStack.Pop();
            }
            _token.Length = 0;
            do
            {
                if (_lineBuffer == null)
                {
                    _linePosition = 0;
                    if (_reader.EndOfStream)
                    {
                        return TokenConstants.TOKEN_EOF;
                    }
                    else
                    {
                        _lineBuffer = _reader.ReadLine();
                        _lineNumber++;
                    }
                }


                for (; _linePosition < _lineBuffer.Length; _linePosition++)
                {
                    char c = _lineBuffer[_linePosition];
                    if (char.IsWhiteSpace(c))
                    {
                        if (_token.Length > 0)
                        {
                            break;
                        }
                    }
                    else
                    {
                        _token.Append(c);
                    }
                }
                if (_linePosition >= _lineBuffer.Length)
                {
                    _lineBuffer = null;
                    if (_token.Length > 0)
                    {
                        if (_token[_token.Length - 1] == CHAR_LINE_CONTINUATION)
                        {
                            _token.Length = _token.Length - 1;
                        }
                        else
                        {
                            ((IParseContext)this).PushToken(TokenConstants.TOKEN_EOL);
                            break;
                        }
                    }
                    else
                    {
                        return TokenConstants.TOKEN_EOL;
                    }
                }
            } while (_token.Length == 0);
            string token = _token.ToString();
            
            if (_parameters != null)
            {
                string value;
                if (_parameters.TryGetValue(token, out value))
                {
                    token = value;
                }
            }
            return token;
        }
        public string ParseToEndOfLine()
        {
            StringBuilder extra = new StringBuilder();
            string token;
            do
            {
                token = ((IParseContext)this).NextToken();
                if (token != TokenConstants.TOKEN_EOL && token != TokenConstants.TOKEN_EOF)
                {
                    if (extra.Length > 0)
                    {
                        extra.Append(" ");
                    }
                    extra.Append(token);
                }
            } while (token != TokenConstants.TOKEN_EOL && token != TokenConstants.TOKEN_EOF);
            return extra.ToString();
        }
        public double ParseDouble()
        {
            return ParseDouble(double.NaN);
        }
        public double ParseDouble(double defaultValue)
        {
            string token = NextToken();
            if (token == TokenConstants.TOKEN_EOL || token == TokenConstants.TOKEN_EOF)
            {
                PushToken(token);
                if (defaultValue == double.NaN)
                {
                    throw new ParseException(_lineNumber, _linePosition, "Missing double numeric value");
                }
                return defaultValue;
            }
            else
            {
                double result;
                if (double.TryParse(token, out result))
                {
                    return result;
                }
                else
                {
                    throw new ParseException(_lineNumber, _linePosition, "Error parsing double numeric value");
                }
            }

        }
        public int ParseInteger()
        {
            return ParseInteger(int.MinValue);
        }
        public int ParseInteger(int defaultValue)
        {
            string token = NextToken();
            if (token == TokenConstants.TOKEN_EOL || token == TokenConstants.TOKEN_EOF)
            {
                PushToken(token);
                if (defaultValue == int.MinValue)
                {
                    throw new ParseException(_lineNumber, _linePosition, "Missing integer value");
                }
                return defaultValue;
            }
            else
            {
                int result;
                if (int.TryParse(token, out result))
                {
                    return result;
                }
                else
                {
                    throw new ParseException(_lineNumber, _linePosition, "Error parsing integer value");
                }
            }

        }
        private const string NO_DEFAULT = "_nodefault_";
        public string ParseString()
        {
            return ParseString(NO_DEFAULT);
        }
        public string ParseString(string defaultValue)
        {
            string token = NextToken();
            if (token == TokenConstants.TOKEN_EOL || token == TokenConstants.TOKEN_EOF)
            {
                PushToken(token);
                if (defaultValue == NO_DEFAULT)
                {
                    throw new ParseException(_lineNumber, _linePosition, "Missing string value");
                }
                return defaultValue;
            }
            else
            {
                return token;
            }

        }
        public void parseUnsupported(IParseContext context)
        {
            string unsupported = ParseToEndOfLine();
            Debug.WriteLine("Unsupported line: " + unsupported, GetType().Name);
        }
        public void parseEndOfLine(IParseContext context)
        {
        }
        #endregion
        public void Parse(Stream stream)
        {
            StreamReader reader = new StreamReader(stream);
            Parse(reader);
        }
        public void Parse(string filePath)
        {
            FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            try
            {
                Parse(stream);
            }
            finally
            {
                stream.Close();
            }
        }
        public void Parse(StreamReader reader)
        {
            Initialize(reader);
            string type = null;
            while (type != TokenConstants.TOKEN_EOF)
            {
                type = NextToken();
                ParseDelegate parser;
                if (_parsers.TryGetValue(type, out parser))
                {
                    parser(this);
                }
                else if (type != TokenConstants.TOKEN_EOF)
                {
                    PushToken(type);
                    _parsers[TokenConstants.TOKEN_UNSUPPORTED](this);
                }
            }
        }
    }
}
